﻿using Delphi.Janus.Util;
using Delphi.Mimir.Config;
using Delphi.Mimir.Data.Entities.Final;
using Delphi.Mimir.Wrapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Delphi.Apoll.Pilot {
    public class ManagedProductTrade {

        public static void CreateManagedProduct(bool activeManaged, decimal initialCashAmount, string name, string code, List<AssetUniverse> aus) {
            using(var context = new DelphiContext()) {
                Symbol sym = new Symbol();
                sym.Code = code;
                sym.Name = name;
                sym.Exchange = context.Exchanges.Where(a => a.Code == "OSPREY").Single();
                sym.ValidFromDate = DateTime.UtcNow;
                sym.ValidTilDate = new DateTime(9999, 12, 31);
                context.Symbols.Add(sym);

                ManagedProduct mp = new ManagedProduct();
                mp.IsActiveManaged = activeManaged;
                mp.Symbol = sym;
                mp.AssetUniverses = aus;
                mp.ValidFromDate = sym.ValidFromDate;
                mp.ValidUntilDate = sym.ValidTilDate;
                context.ManagedProducts.Add(mp);

                CashTransaction ctx = new CashTransaction();
                ctx.Amount = initialCashAmount;
                ctx.Currency = context.Currencies.Where(a => a.Code == "USD").Single();
                ctx.AmountRefCurrency = initialCashAmount;
                ctx.ManagedProduct = mp;
                ctx.TransactionDate = DateTime.UtcNow;
                context.CashTransactions.Add(ctx);

                var err = context.GetValidationErrors();
                context.SaveChanges();
            }
        }

        public static void SimulatedTrade(int prodId, int symbolId, int quantity, decimal maxAmount, DateTime tradeDate) {
            using(var context = new DelphiContext()) {
                ManagedProduct mp = new ManagedProduct();
                Symbol sym = new Symbol();
                OrderConstraint oc = new OrderConstraint();
                ManagedProductPosition mpp = new ManagedProductPosition();
                Order o = new Order();
                OrderExecution oexc = new OrderExecution();
                Quote q = new Quote();
                CashTransaction ct = new CashTransaction();
                int executedQuantity;
                decimal cashBalance;

                int fee = 10;
                

                // make sure, that the entities are not null --> Single() will through exception when db returns null
                try {
                    mp = context.ManagedProducts.Where(a => a.ManagedProductId == prodId).Single();
                    sym = context.Symbols.Where(a => a.SymbolId == symbolId).Single();
                    oc = context.OrderConstraints.Where(a => a.OrderConstraintCode == "ONC").Single();
                    // get the last quote of symbol
                    q = context.Quotes.Where(a => a.SymbolId == symbolId && a.TradeDate == tradeDate && a.FrequencyId == 7).Single();
                } catch {
                    return;
                }
                cashBalance = mp.CashBalance(tradeDate);

                // Is the Symbol in the AssetUniverse?
                foreach(AssetUniverse au in mp.AssetUniverses) {
                    // TODO implement that asset universe hack....
                    
                }
                
                // Check whether Position already exists...
                mpp = context.ManagedProductPositions
                    .Where(a => a.SymbolId == symbolId 
                            && a.ManagedProductId == prodId
                            && a.ValidUntilDate > tradeDate).SingleOrDefault();

                // set max amount to available funds
                if(maxAmount > cashBalance) {
                    maxAmount = cashBalance;
                } else if((maxAmount == 0) && ((quantity * q.Close + fee) > cashBalance)) {
                    maxAmount = cashBalance;
                }

                // calculate executed quantity (not exceed max amount)
                if(maxAmount == 0) {
                    // No max amount.... go with given quantity
                    executedQuantity = quantity;
                } else {
                    // ottherwise do not exceed max amount
                    executedQuantity = Decimal.ToInt32(Decimal.Truncate((maxAmount - fee) / q.Close));
                    // but not more than quantity... but if quantity == 0 then use maxAmount.
                    if(quantity != 0) {
                        executedQuantity = Math.Min(executedQuantity, quantity);
                    }
                }

                // Check if there is enough
                

                // create new position when mpp is null
                if (mpp == null) {
                    // short positions are not allowed
                    if(executedQuantity < 0) {
                        return;
                    }
                    mpp = new ManagedProductPosition();
                    mpp.ManagedProduct = mp;
                    mpp.Symbol = sym;
                    mpp.ValidFromDate = tradeDate;
                    mpp.ValidUntilDate = new DateTime(9999, 12, 31);
                    mpp.Quantity = executedQuantity;

                    context.ManagedProductPositions.Add(mpp);
                } else {
                    // check whether the trade on existing position will lead to short position...
                    if((mpp.Quantity + executedQuantity) < 0) {
                        return;
                    } else if ((mpp.Quantity + executedQuantity) == 0) {
                        // trade will close position
                        mpp.ValidUntilDate = tradeDate;
                    }
                    // add executquantity to existing quantity
                    mpp.Quantity += executedQuantity;
                }
                
                // Create Order
                o.Cancelled = false;
                o.EntryDate = tradeDate;
                o.Executed = true;
                o.ManagedProduct = mp;
                o.MaximalAmount = maxAmount;
                o.OrderConstraint = oc;
                o.Quantity = executedQuantity;
                o.Symbol = sym;
                o.ValidFromDate = tradeDate;
                o.ValidUntilDate = tradeDate;

                context.Orders.Add(o);
                
                oexc.ExecutionDate = tradeDate;
                oexc.Fees = fee;
                oexc.Order = o;
                oexc.PartialExecution = false;
                oexc.Quantity = executedQuantity;
                oexc.Quote = q.Close;

                context.OrderExecutions.Add(oexc);

                ct.Amount = (oexc.NetAmount + oexc.Fees) * -1;
                ct.Currency = context.Currencies.Where(a => a.Code == "USD").SingleOrDefault();
                ct.TransactionDate = tradeDate;
                ct.ManagedProduct = mp;
                ct.AmountRefCurrency = ct.Amount;
                context.CashTransactions.Add(ct);
                context.SaveChanges();

            // Jun 5 til 14
            }
        }

        public static void CalculateQuotes(string managedProductCode, DateTime startDate, DateTime endDate) {
            using(var context = new DelphiContext()) {
                ManagedProduct mp = new ManagedProduct();
                Symbol sym = new Symbol();
                Frequency freq = new Frequency();

                try {
                    mp = context.ManagedProducts.Where(a => a.Symbol.Code == managedProductCode).Single();
                    sym = context.Symbols.Where(a => a.SymbolId == mp.ManagedProductId).Single();
                    freq = context.Frequencies.Where(a => a.Code == "D").Single();
                } catch {
                    return;
                }

                List<Quote> quotes = new List<Quote>();
                DateTime tempDate = startDate > mp.ValidFromDate ? startDate : mp.ValidFromDate;
                while(tempDate <= endDate) {
                    Quote q = new Quote();
                    var balance = mp.NetAssetValue(tempDate) / 1000;
                    q.SymbolId = sym.SymbolId;
                    q.Open = balance;
                    q.High = balance;
                    q.Low = balance;
                    q.Close = balance;
                    q.Volume = 0;
                    q.TradeDate = tempDate;
                    q.FrequencyId = freq.CategoryId;
                    q.OpenInterest = 0;
                    tempDate = tempDate.NextBusinessDay();
                    quotes.Add(q);
                }
                context.Quotes.AddRange(quotes);
                context.SaveChanges();
                
            }
        }

        public static void CalculateQuotes(string managedProductCode) {
            using (var context = new DelphiContext()) {
                ManagedProduct mp = new ManagedProduct();
                Symbol sym = new Symbol();

                try {
                    mp = context.ManagedProducts.Where(a => a.Symbol.Code == managedProductCode).Single();
                    sym = context.Symbols.Where(a => a.SymbolId == mp.ManagedProductId).Single();
                } catch {
                    return;
                }
                DateTime startDate = SymbolWrapper.GatLatestTradeDate(sym) ?? mp.ValidFromDate;
                startDate = startDate.NextBusinessDay();

                CalculateQuotes(managedProductCode, startDate, DateTime.Now.Date);
            }
        }

        public static void Rebalance(string managedProductCode, DateTime startDate, DateTime endDate, int symbolId, double ratio, double trigger) {
            using (var context = new DelphiContext()) {
                ManagedProduct mp = new ManagedProduct();
                Symbol sym = new Symbol();
                Frequency freq = new Frequency();
                Symbol symbol = new Symbol();
                List<ManagedProductPosition> mpps = new List<ManagedProductPosition>();

                try {
                    mp = context.ManagedProducts.Where(a => a.Symbol.Code == managedProductCode).Single();
                    sym = context.Symbols.Where(a => a.SymbolId == mp.ManagedProductId).Single();
                    freq = context.Frequencies.Where(a => a.Code == "D").Single();
                    symbol = context.Symbols.Where(a => a.SymbolId == symbolId).Single();
                    mpps = context.ManagedProductPositions.Where(a => a.ManagedProductId == mp.ManagedProductId).ToList();
                } catch {
                    return;
                }

                decimal netAssetValue = 0;
                decimal value = 0;
                double currentRatio = 0;

                DateTime tempDate = SymbolWrapper.GatLatestTradeDate(sym) ?? mp.ValidFromDate;
                int quantity = mpps.Where(a => a.SymbolId == symbolId).SingleOrDefault().Quantity;
                
                while(tempDate <= DateTime.UtcNow.Date) {
                    netAssetValue = mp.NetAssetValue(tempDate);
                    value = quantity * context.Quotes.Where(a => a.SymbolId == symbolId).SingleOrDefault().Close;
                    currentRatio = (double)(value / netAssetValue);
                    
                    // bellow threshold --> buy and sell the other to bring ratio back
                    if(currentRatio < (ratio - trigger)) {

                    }

                    tempDate = tempDate.NextBusinessDay();
                }

            }
        }
    }
}
